#ifndef FUNCTIONS_HPP
#define FUNCTIONS_HPP

#include "meshTypes_fwd.hpp"
#include <filesystem>
#include <optional>
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <regex>


/* smaller functions that don't warrant their own category go into util,
 * until there are enough functions of a similar category to transfer them 
 * to their own distinct file. Thus, the number of functions in this file
 * should always be as low as possible */

namespace hms
{
	

template<typename Derived>
using ScalarTypeIfColumnVector = std::enable_if_t<
	Derived::ColsAtCompileTime==1,
	typename Derived::Scalar
>;


/**
 * @brief Returns the difference between second and first entry of a 2D vector.
 * 
 * @param vec Must be a 2D vector type (e.g. Vector2s, Array2i, etc.)
 * @return ScalarTypeIfVector<Derived> 
 * 
 * Tiny helper function. Only to be called on 2D vectors, otherwise an assertion
 * is triggered.
 */
template<typename Derived>
ScalarTypeIfColumnVector<Derived> diff( const Eigen::DenseBase<Derived>& vec );

/* SFINAE return type aliases to perform exact comparison only for integral
 * types */
template<typename... Ts>
using boolIfIntegral = typename std::enable_if_t<
	std::conjunction_v< std::is_integral<Ts>... >,
	bool>;


/**
 * @brief Performs exact comparison using Eigen::operator== for
 * Eigen::MatrixBase types.
 * 
 * @tparam Derived Eigen::MatrixBase type whose scalar is an integral type
 * @tparam OtherDerived Eigen::MatrixBase type whose scalar is an integral type
 * @return boolIfIntegral<typename Derived::Scalar, typename OtherDerived::Scalar>
 * Function only defined for integral Scalar types.
 * 
 * This function is exactly equivalent to using Eigen::operator==. Its
 * usefulness comes from its overloads, which allow using it with mixed 
 * Eigen::MatrixBase and Eigen::ArrayBase types.
 */
template<typename Derived, typename OtherDerived>
boolIfIntegral<typename Derived::Scalar, typename OtherDerived::Scalar>
exactlyEqual (
	const Eigen::DenseBase<Derived>& derived,
	const Eigen::DenseBase<OtherDerived>& other
);



/**
 * @brief Returns true if two floating point values are within a relative
 * epsilon and false otherwise.
 * 
 * The function implements "approximatelyEqual" from Knuth's "The Art of
 * Computer Programming":
 * 
 * @code
 *               _
 *              |   |b| * epsilon, if |a| < |b|
 * |a - b| <=  < 
 *              |_  |a| * epsilon, if |b| < |a|
 * @endcode
 * 
 * The epsilon value used is the hms::epsilon defined in globals.hpp.
 */
bool approxEqual(scalar, scalar);

/**
 * @brief Passes hms::epsilon to Eigen's isApprox() function to test for
 * approximate equality of two Eigen types.
 * 
 * @tparam Derived Type of the first argument. Must be derived from
 * Eigen::MatrixBase.
 * @tparam OtherDerived Type of the second argument. Must be derived from
 * Eigen::MatrixBase 
 * @return true according to the implementation of Eigen::DenseBase::isApprox()
 * in the Eigen library.
 * @return false otherwise.
 * 
 * @see approxEqual(scalar, scalar)
 * 
 * Uses Eigen's multiplicative isApprox() function, which is faster than a
 * coefficient-wise approxEqual(scalar, scalar). However, it does not
 * necessarily produce the same result!
 */
template<typename Derived, typename OtherDerived>
bool approxEqual(
	const Eigen::DenseBase<Derived>& derived,
	const Eigen::DenseBase<OtherDerived>& other
);

template<class T, class... Ts>
struct are_same : std::conjunction<std::is_same<T, Ts>...> {};

template<class T, class... Ts>
inline constexpr bool are_same_v = are_same<T, Ts...>::value;


template<typename ... Ts>
using boolIfScalars = std::enable_if_t< are_same_v<scalar, Ts ...>, bool>;

/**
 * @overload for an arbitrary number of scalars
 */
template<typename ... Ts>
boolIfScalars<Ts...> approxEqual( Ts ... vals );


/**
 * @brief Swaps specified columns of an arbitrary amount of Eigen types. Not
 * really necessary, because we're saving only one line per array,
 * but it doesn't harm.
 * 
 * @tparam T 
 * @tparam Ts 
 * @param a 
 * @param b 
 * @param firstToSwap 
 * @param restToSwap 
 */
template<typename T, typename... Ts>
void swapCols(
	Index a, Index b,
	Eigen::DenseBase<T>& firstToSwap,
	Eigen::DenseBase<Ts>& ... restToSwap
);



/* interpolates linearly between the given values a and b, according to the
 * given ratio */
template<typename T>
auto interpolate( T a, T b, double ratio )
-> typename std::enable_if_t<std::is_floating_point_v<T>,T>;

template<typename Derived>
auto interpolate(
	const Eigen::DenseBase<Derived>& a,
	const Eigen::DenseBase<Derived>& b,
	double ratio
) -> decltype(auto);

/* prints and updates a percentage value.*/
void printProgress(
	Index min,
	Index current,
	Index max
);

/* returns true if both arrays have the same entries, regardless of order */
template<typename T>
bool sameEntries( const std::vector<T>&, const std::vector<T>& );

bool sameEntries(
	const Ref<const ArrayXi>& a,
	const Ref<const ArrayXi>& b
);

bool sameEntries(
	const Ref<const ArrayXi>& a,
	const IdxVector& b
);

bool sameEntries(
	const IdxVector& a,
	const Ref<const ArrayXi>& b
);

/*both 1D and 2D arrays of integral types can be searched for a specific entry*/
template<typename T>
auto hasEntry( const Ref<const ArrayXi>&, T ) -> boolIfIntegral<T>;

template<typename T, typename U>
auto hasEntry( const std::vector<T>&, U )-> boolIfIntegral<T,U>;

template<typename T, typename U>
auto hasEntry( const std::vector<std::vector<T>>&, U ) -> boolIfIntegral<T,U>;

template<typename T>
auto hasEntry( const std::vector<std::vector<T>>&, const std::vector<T>& )
 -> boolIfIntegral<T>;


template<typename Derived>
std::vector<typename Derived::Scalar> toVector(const Eigen::DenseBase<Derived>&);

template<typename Derived, typename T>
auto toEigen( const std::vector<T>& )
	-> std::enable_if_t<std::is_fundamental_v<T>, Derived>;

template<typename Derived1, typename Derived2>
auto toEigen( const std::vector<Derived2>& )
	-> std::enable_if_t
		<std::is_base_of_v<Eigen::DenseBase<Derived2>, Derived2>, Derived1>;


/**
 * @brief Uses std::lower_bound to perform binary search for the specified key
 * among the provided key-value-pairs. This means that it will find the first
 * key that compares greater or equal to the provided one. No check is 
 * performed to make sure the iterator points to a key that compares equal.
 * 
 * @tparam Key 
 * @tparam Value 
 * @param v std::vector holding the key-value-pairs. Must be sorted!
 * @param key The key to be searched.
 * @return std::vector<std::pair<Key, Value>>::const_iterator Points to the
 * first key that compares greater or equal to key.
 * 
 * @see getKeyIndex()
 */
template<typename Key, typename Value>
auto findKey( const std::vector<std::pair<Key, Value>>& v, const Key& key )
	-> typename std::vector<std::pair<Key, Value>>::const_iterator;

/**
 * @brief Returns the index of a key-value-pair in a std::vector.
 * 
 * @tparam Key 
 * @tparam Value 
 * @param v std::vector holding the key-value-pairs. Must be sorted!
 * @param key The key to be searched.
 * @return int 
 * 
 * @see findKey()
 */
template<typename Key, typename Value>
int getKeyIndex( const std::vector<std::pair<Key, Value>>& v, const Key& key );


template<typename Key, typename Value>
void sortByKeys( std::vector<std::pair<Key, Value>>& v );

/**
 * @brief For two ranges A and B, defined by their minimum and maximum, this
 * function returns their overlap, again as minimum and maximum, if such an
 * overlap exists.
 * 
 * @param aBeg Minimum of range A (inclusive).
 * @param aEnd Maximum of range A (inclusive).
 * @param bBeg Minimum of range B (inclusive).
 * @param bEnd Maximum of range B (inclusive).
 * @return std::optional<Array2i> Holds the minimum and maximum of the overlap,
 * if such an overlap exists.
 * 
 * Example 1: A = { 0, 1, 2, 3 }, B = { 2, 3, 4 }.
 * Then aBeg = 0, aEnd = 3, bBeg = 2 and bEnd = 4.
 * The return value inside the std::optional is { 2, 3 }.
 * Example 2: A = { 0, 1, 2, 3 }, B = { 4, 5, 6 }.
 * Then aBeg = 0, aEnd = 3, bBeg = 4 and bEnd = 6.
 * The return value is std::nullopt (contextually converts to boolean false).
 */
std::optional<Array2i> overlap(Index aBeg, Index aEnd, Index bBeg, Index bEnd);

/**
 * @overload
 * 
 * @param a Minimum and maximum of first range.
 * @param b Minimum and maximum of second range.
 * @return std::optional<Array2i> 
 */
std::optional<Array2i> overlap(
	const Ref<const Array2i>& a,
	const Ref<const Array2i>& b
);

std::optional<Array2i> overlap_usingSizes(
	Index aBeg, Index aSize, Index bBeg, Index bSize
);


/** @brief Returns the beginning and size of segments for each OpenMP-thread
 * for a specified number of items n.
 * If _OPENMP is not defined, returns {0, n} */
template<typename IntegerType>
std::pair<IntegerType, IntegerType> ompSegment( IntegerType n );

/** @brief Trim leading whitespace of string 
 * returns the trimmed string*/
std::string ltrim(const std::string&);

/** @brief Trim trailing whitespace of string 
 * returns the trimmed string*/
std::string rtrim(const std::string&);
 
/** @brief Trim leading and trailing whitespace of string 
 * returns the trimmed string*/
std::string trim(const std::string&);

template<typename Derived>
std::string sizeString( const Eigen::EigenBase<Derived>& );


namespace fs = std::filesystem;

fs::path subdirOrAbsolute( const fs::path& wd, const std::string& path );

scalar degToRad(scalar angle);

} // end namespace hms


/* source file for template definitions */
#include "util.tpp"

#endif